use std::sync::Arc;

use log::warn;
use openssl::{
    dh::Dh,
    error::ErrorStack,
    ssl::{
        ClientHelloError, SniError, SslAlert, SslContext, SslMethod,
        SslOptions, SslRef, SslVersion, TlsExtType,
    },
    x509::X509,
};

use super::{
    base::{ctx_builder, parse_extension_value, SslInitialization},
    ffi::{
        ssl_certs_clear, ssl_clear_options, ssl_set_groups_list,
        ssl_set_options,
    },
};

use crate::{
    config::ssl::acceptor::{
        SslAcceptorRuntimeConf, SslProtoVersion, FFDHE_2048,
    },
    error::*,
};

// params: sni, listen_address
pub type LookupSslAcceptorConfCallback = Box<
    dyn Fn(Option<&String>, &str) -> Result<Option<Arc<SslAcceptorRuntimeConf>>>
        + 'static
        + Sync
        + Send,
>;

#[allow(dead_code)]

pub struct CustomAcceptorBuilder {
    conf: Arc<SslAcceptorRuntimeConf>,
    dynamic_conf_callback: Option<LookupSslAcceptorConfCallback>,
}

#[allow(dead_code)]
impl CustomAcceptorBuilder {
    pub fn new(conf: Arc<SslAcceptorRuntimeConf>) -> Self {
        CustomAcceptorBuilder {
            conf,
            dynamic_conf_callback: None,
        }
    }

    pub fn set_dynamic_conf_callback(
        &mut self,
        callback: LookupSslAcceptorConfCallback,
    ) {
        self.dynamic_conf_callback = Some(callback);
    }

    pub fn build(self) -> Result<SslContext, ErrorStack> {
        let ssl_initlization = SslInitialization::get_instance();

        let mut ctx_builder = ctx_builder(SslMethod::tls_server())?;
        ctx_builder.set_min_proto_version(Some(SslVersion::SSL3))?;
        ctx_builder.set_max_proto_version(Some(SslVersion::TLS1_3))?;

        let conf = &self.conf;

        if let Some(proto_version) = conf.proto_version {
            if proto_version & SslProtoVersion::SSL3 == 0 {
                ctx_builder.set_options(SslOptions::NO_SSLV3);
            }

            if proto_version & SslProtoVersion::TLS1 == 0 {
                ctx_builder.set_options(SslOptions::NO_TLSV1);
            }

            if proto_version & SslProtoVersion::TLS1_1 == 0 {
                ctx_builder.set_options(SslOptions::NO_TLSV1_1);
            }

            if proto_version & SslProtoVersion::TLS1_2 == 0 {
                ctx_builder.set_options(SslOptions::NO_TLSV1_2);
            }

            if proto_version & SslProtoVersion::TLS1_3 == 0 {
                ctx_builder.set_options(SslOptions::NO_TLSV1_3);
            }
        }

        let dh = Dh::params_from_pem(FFDHE_2048.as_bytes())?;
        ctx_builder.set_tmp_dh(&dh)?;

        if !conf.groups_list.is_empty() {
            ctx_builder.set_groups_list(&conf.groups_list)?;
        }

        if !conf.cipher_list.is_empty() {
            ctx_builder.set_cipher_list(&conf.cipher_list)?;
        }

        if !conf.ciphersuites.is_empty() {
            ctx_builder.set_ciphersuites(&conf.ciphersuites)?;
        }

        if conf.enable_ntls {
            ctx_builder.enable_ntls();
        }

        if let Some(cert) = conf.get_leaf_cert() {
            ctx_builder.set_certificate(cert)?;
        }

        for v in conf.get_chain_certs() {
            let x509 = X509::from_der(v).unwrap();
            ctx_builder.add_extra_chain_cert(x509)?;
        }

        if let Some(pkey) = conf.pkey.as_ref() {
            ctx_builder.set_private_key(pkey)?;
        }

        // TODO: 国密证书支持

        let arc_self = Arc::new(self);
        ctx_builder.set_ex_data(
            ssl_initlization.acceptor_builer_index,
            arc_self.clone(),
        );

        if arc_self.dynamic_conf_callback.is_some() {
            ctx_builder.set_client_hello_callback(move |ssl_ref, ssl_alert| {
                let ssl_initlization = SslInitialization::get_instance();
                let custom_acceptor = ssl_ref
                    .ssl_context()
                    .ex_data(ssl_initlization.acceptor_builer_index)
                    .unwrap()
                    .clone();

                custom_acceptor.client_hello_callback(ssl_ref, ssl_alert)
            });

            ctx_builder.set_servername_callback(move |ssl_ref, ssl_alert| {
                let ssl_initlization = SslInitialization::get_instance();
                let custom_acceptor = ssl_ref
                    .ssl_context()
                    .ex_data(ssl_initlization.acceptor_builer_index)
                    .unwrap()
                    .clone();
                custom_acceptor.servername_callback(ssl_ref, ssl_alert)
            });
        }

        Ok(ctx_builder.build())
    }
}

impl CustomAcceptorBuilder {
    fn client_hello_callback(
        &self,
        ssl_ref: &mut SslRef,
        ssl_alert: &mut SslAlert,
    ) -> Result<(), ClientHelloError> {
        _ = ssl_alert;

        let ssl_initlization = SslInitialization::get_instance();
        let conn_local_address =
            match ssl_ref.ex_data(ssl_initlization.conn_local_address_index) {
                Some(address) => address,
                None => "",
            };

        let conn_peer_address =
            match ssl_ref.ex_data(ssl_initlization.conn_peer_address_index) {
                Some(address) => address,
                None => "",
            };

        let listener_address =
            match ssl_ref.ex_data(ssl_initlization.listener_address_index) {
                Some(address) => address,
                None => "",
            };

        let sni = match ssl_ref.client_hello_ext(TlsExtType::SERVER_NAME) {
            Some(ext) => match parse_extension_value(ext) {
                Ok(host_name) => Some(host_name),
                Err(e) => {
                    warn!(
                        "parse parse_extension_value failed, e: {}, client: {}, server: {}",
                        e, conn_peer_address, conn_local_address
                    );
                    return Err(ClientHelloError::ERROR);
                }
            },
            None => None,
        };

        let callback = match self.dynamic_conf_callback.as_ref() {
            Some(callback) => callback,
            None => {
                return Ok(());
            }
        };

        let acceptor_conf_option = match callback(
            sni.as_ref(),
            listener_address,
        ) {
            Ok(option_c) => option_c,
            Err(e) => {
                warn!(
                    "lookup dynamic ssl acceptor conf failed, e: {}, client: {}, server: {}",
                    e, conn_peer_address, conn_local_address
                );
                return Err(ClientHelloError::ERROR);
            }
        };

        let conf = match acceptor_conf_option {
            Some(conf) => conf,
            None => {
                return Ok(());
            }
        };

        // get in servername callback.
        ssl_ref.set_ex_data(ssl_initlization.acceptor_conf_index, conf.clone());

        let conf_proto_version = conf.proto_version.unwrap_or(0);

        // TODO: 将conf绑定到ssl上.
        let def_conf = &self.conf;
        let def_proto_version = def_conf.proto_version.unwrap_or(0);

        if conf_proto_version != def_proto_version {
            if conf_proto_version & SslProtoVersion::SSL3 == 0 {
                ssl_set_options(ssl_ref, SslOptions::NO_SSLV3);
            } else {
                ssl_clear_options(ssl_ref, SslOptions::NO_SSLV3);
            }

            if conf_proto_version & SslProtoVersion::TLS1 == 0 {
                ssl_set_options(ssl_ref, SslOptions::NO_TLSV1);
            } else {
                ssl_clear_options(ssl_ref, SslOptions::NO_TLSV1);
            }

            if conf_proto_version & SslProtoVersion::TLS1_1 == 0 {
                ssl_set_options(ssl_ref, SslOptions::NO_TLSV1_1);
            } else {
                ssl_clear_options(ssl_ref, SslOptions::NO_TLSV1_1);
            }

            if conf_proto_version & SslProtoVersion::TLS1_2 == 0 {
                ssl_set_options(ssl_ref, SslOptions::NO_TLSV1_2);
            } else {
                ssl_clear_options(ssl_ref, SslOptions::NO_TLSV1_2);
            }

            if conf_proto_version & SslProtoVersion::TLS1_3 == 0 {
                ssl_set_options(ssl_ref, SslOptions::NO_TLSV1_3);
            } else {
                ssl_clear_options(ssl_ref, SslOptions::NO_TLSV1_3);
            }
        }

        if !conf.groups_list.is_empty()
            && conf.groups_list != def_conf.groups_list
        {
            _ = ssl_set_groups_list(ssl_ref, &conf.groups_list);
        }

        if !conf.cipher_list.is_empty()
            && conf.cipher_list != def_conf.cipher_list
        {
            _ = ssl_ref.set_cipher_list(&conf.cipher_list);
        }

        if !conf.ciphersuites.is_empty()
            && conf.ciphersuites != def_conf.ciphersuites
        {
            _ = ssl_ref.set_ciphersuites(&conf.ciphersuites);
        }

        if conf.enable_ntls != def_conf.enable_ntls {
            if conf.enable_ntls {
                ssl_ref.enable_ntls();
            } else {
                ssl_ref.disable_ntls();
            }
        }

        Ok(())
    }

    fn servername_callback(
        &self,
        ssl_ref: &mut SslRef,
        ssl_alert: &mut SslAlert,
    ) -> Result<(), SniError> {
        let ssl_initlization = SslInitialization::get_instance();
        let acceptor_conf =
            match ssl_ref.ex_data(ssl_initlization.acceptor_conf_index) {
                Some(acceptor_conf) => acceptor_conf.clone(),
                None => {
                    return Ok(());
                }
            };

        let leaf_cert = acceptor_conf.get_leaf_cert();
        if leaf_cert.is_some() {
            ssl_certs_clear(ssl_ref);

            ssl_ref.set_certificate(leaf_cert.unwrap()).map_err(|e| {
                _ = e;
                SniError::ALERT_FATAL
            })?;

            for v in acceptor_conf.get_chain_certs() {
                let x509 = X509::from_der(v).unwrap();
                ssl_ref.add_chain_cert(x509).map_err(|e| {
                    _ = e;
                    SniError::ALERT_FATAL
                })?;
            }

            if let Some(pkey) = &acceptor_conf.pkey {
                ssl_ref.set_private_key(pkey).map_err(|e| {
                    _ = e;
                    SniError::ALERT_FATAL
                })?;
            }
        }

        _ = ssl_alert;
        Ok(())
    }
}
